#ifndef __CHANNEL_UDP_included
#define __CHANNEL_UDP_included

#include "Channel.h"
#include "../engine/PackageFactory.h"
#include "../port/System.h"

using namespace boost;
using namespace boost::asio;
using namespace boost::asio::ip;
using namespace Engine;

class CChannelUDP : public CChannel {

	io_service mIOService;
	boost::shared_ptr<udp::socket> mSocket;
	udp::endpoint mRecvRemoteEndpoint;
	udp::endpoint mSendRemoteEndpoint;
	char mReceiveBuffer[1500];

	void HandleReceive(const system::error_code& error, std::size_t bytes_transferred) {
		
		if (!error || error == error::message_size) {
			boost::asio::streambuf buf;
			buf.sputn(mReceiveBuffer, bytes_transferred);

			try {
				boost::shared_ptr<CPackage> p = CPackageFactory::Parse(buf, mRequestAllocator.get());
				p->AddPropertie(string(PACKAGE_PROP_SERV_IP), mRecvRemoteEndpoint.address().to_string());
				if(mPackageReceiver) mPackageReceiver->OnReceive(p);
			}
			catch(CPackageParseException e) {
				DebugStream << '[' << e.what() << "] " << *get_error_info<parse_error>(e);
			}
			catch (std::exception& e)
			{
				DebugStream << e.what() << '\n';
			}

			mSocket->async_receive_from(
					buffer(mReceiveBuffer, sizeof(mReceiveBuffer)), 
					mRecvRemoteEndpoint,
					boost::bind(
							&CChannelUDP::HandleReceive, 
							this, 
							asio::placeholders::error, 
							asio::placeholders::bytes_transferred
							)
					);
		}
		else {
			DebugStream << "Receive error : " << error.message() << '\n';
		}
	}

public :

	CChannelUDP(int local_port) {	
		SetupLocal(local_port);
	}

	virtual ~CChannelUDP() {
		Close();
	}

	virtual void ScheduleReceive(PackageReceiverRef receiver) {
		if(!mSocket) return;
		mPackageReceiver = receiver;
		mSocket->async_receive_from(
					buffer(mReceiveBuffer, sizeof(mReceiveBuffer)), 
					mRecvRemoteEndpoint,
					boost::bind(
							&CChannelUDP::HandleReceive, 
							this, 
							asio::placeholders::error, 
							asio::placeholders::bytes_transferred
							)
					);
		mIOService.run();
	}

	virtual void SetupLocal(int local_port) {
		mSocket = boost::shared_ptr<udp::socket>(new udp::socket(mIOService, udp::endpoint(udp::v4(), local_port)));
	}

	virtual void SetupRemote(std::string& remote_addr, int remote_port) {
		mSendRemoteEndpoint = udp::endpoint(ip::address::from_string(remote_addr), remote_port);
	}

	virtual void Close() {
		mSocket->close();
		mIOService.stop();
		CChannel::Close();
	}

	virtual void Send(boost::shared_ptr<Engine::CPackage> p) {

		boost::asio::streambuf buf;
		std::ostream os(&buf);
		p->GetData(os);

		mSocket->send_to(buf.data(), mSendRemoteEndpoint);
	}

	virtual boost::shared_ptr<CPackage> ReceivePackage() {

		boost::asio::streambuf buf;
		int n = mSocket->receive_from(buf.prepare(1500), mRecvRemoteEndpoint);
		if(n <= 0) {
			throw new std::exception("ChannelUDP::ReceivePackage - socket error");
		}

		buf.commit(n);
		boost::shared_ptr<CPackage> p;
		try {
			p = CPackageFactory::Parse(buf, mRequestAllocator.get());
			p->AddPropertie(string(PACKAGE_PROP_SERV_IP), mRecvRemoteEndpoint.address().to_string());
		}
		catch(std::exception& e) {
			DebugStream << e.what() << '\n';
		}
		
		return p;
	}

	virtual void GetLocalIPAddress(string& ip) {
		ip = Util::CSystem::GetHostIPAddress();
		//ip = mSocket->local_endpoint().address().to_string();
	}

	virtual void GetRemoteIPAddress(string& ip) {
		ip = mRecvRemoteEndpoint.address().to_string();
	}
};

#endif //__CHANNEL_UDP_included